//////////////////////////////////////////////
// UniquePtr.h
//
//////////////////////////////////////////////

/// Defines / Macros -------------------------

#pragma once

/// Includes ---------------------------------

// Standards
#include <memory>

/// Class ------------------------------------

namespace nkMemory
{
	template <typename T>
	class UniquePtr
	{
		public :

			// Constructors
			UniquePtr () noexcept
			:	_data (nullptr)
			{
				// Nothing to do
			}

			UniquePtr (T* data) noexcept
			:	_data (data)
			{
				// Nothing to do
			}

			template <typename U, typename = std::enable_if_t<std::is_base_of_v<T, U>>>
			UniquePtr (U* data) noexcept
			:	_data (data)
			{
				// Nothing to do
			}

			UniquePtr (const UniquePtr<T>& other) = delete ;

			UniquePtr (UniquePtr<T>&& other) noexcept
			:	_data (other._data)
			{
				// Reset input
				other._data = nullptr ;
			}

			template <typename U, typename = std::enable_if_t<std::is_base_of_v<T, U>>>
			UniquePtr (UniquePtr<U>&& other) noexcept
			:	_data (other.get())
			{
				// Reset input
				other.release() ;
			}

			// Destructor
			~UniquePtr ()
			{
				delete _data ;
				_data = nullptr ;
			}

		public :

			// Getters
			T* get () const
			{
				return _data ;
			}

			bool empty () const
			{
				return !_data ;
			}

		public :

			// Management
			T* release ()
			{
				T* ptr = _data ;
				_data = nullptr ;

				return ptr ;
			}

			void reset (T* data = nullptr)
			{
				delete _data ;
				_data = data ;
			}

		public :

			// Operators
			T& operator* ()
			{
				return *_data ;
			}

			const T& operator* () const
			{
				return *_data ;
			}

			T* operator-> ()
			{
				return _data ;
			}

			const T* operator-> () const
			{
				return _data ;
			}
			
			UniquePtr<T>& operator= (const UniquePtr<T>& other) = delete ;

			UniquePtr<T>& operator= (UniquePtr<T>&& other) noexcept
			{
				delete _data ;
				_data = other._data ;

				other._data = nullptr ;

				return *this ;
			}

			operator bool () const
			{
				return _data ;
			}

			bool operator! () const
			{
				return !_data ;
			}

			operator std::unique_ptr<T> ()
			{
				T* data = _data ;
				_data = nullptr ;

				return std::unique_ptr<T>(data) ;
			}

			template <typename U, typename = std::enable_if_t<std::is_base_of_v<U, T>>>
			operator std::unique_ptr<U> ()
			{
				T* data = _data ;
				_data = nullptr ;

				return std::unique_ptr<T>(data) ;
			}

		public :

			// Templated constructors
			UniquePtr (std::unique_ptr<T> stdPtr) noexcept
			:	_data (stdPtr.get())
			{
				// Reset source
				stdPtr.release() ;
			}

			template <typename U, typename = std::enable_if_t<std::is_base_of_v<T, U>>>
			UniquePtr (std::unique_ptr<U> stdPtr) noexcept
			:	_data (stdPtr.get())
			{
				// Reset source
				stdPtr.release() ;
			}

		private :

			// Attributes
			// Base buffer
			T* _data ;
	} ;

	// Emulation
	template <typename T, typename... Args>
	static UniquePtr<T> makeUnique (Args&&... args)
	{
		return new T (std::forward<Args>(args)...) ;
	}
}